package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 聊天室服务端
 */
public class Server {
    /*
       java.net.ServerSocket
       运行在服务端，相当于客服的"总机"。用于接收不同客户端的连接
    */
    private ServerSocket serverSocket;

    //线程池
    private ExecutorService threadPool;

    //该集合用于存放所有客户端的输出流
//    private List<PrintWriter> allOut = new ArrayList<>();
    //使用并发安全的集合
//    private List<PrintWriter> allOut = Collections.synchronizedList(new ArrayList<>());
    private Map<String, PrintWriter> allOut = Collections.synchronizedMap(new HashMap<>());

    public Server() {
        try {

            /*
                实例化ServerSocket时需要指定端口，客户端的Socket就是通过这个端口找到
                ServerSocket并与之连接的
                如果该端口被其他应用程序占用会抛出异常:
                java.net.BindException:address already in use
                此时需要更换端口再次尝试
             */
            System.out.println("正在启动服务器...");
            serverSocket = new ServerSocket(8088);
            threadPool = Executors.newFixedThreadPool(50);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            /*
                ServerSocket有一个重要的方法:
                Socket accept()
                该方法用于接受客户端的连接，这个方法是一个阻塞方法，调用该方法后不会立即返回
                此时会"卡主"，直到一个客户端连接方法才会返回，返回的Socket就是与该客户端建立
                连接的。
             */
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println(socket.getInetAddress().getHostAddress() + "连接了!");
                //启动线程与该客户交互
                ClientHandler handler = new ClientHandler(socket);
                threadPool.execute(handler);
//                Thread t = new Thread(handerler);
//                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /*
        该线程任务负责与指定客户端交互
        当类的内部需要传入一个外部值时, 可以通过在方法内创建相关属性后重写有参构造的方法, 然后新建对象时传入外部值即可
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录该客户端的IP地址
        private String nickName;//记录该客户端的昵称

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机的IP地址信息
            this.host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;
            try {
                /*
                Socket提供的方法:
                InputStream getInputStream()
                通过Socket获取一个字节输入流，用于读取来自远端计算机发送过来的字节数据
             */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                ObjectInputStream ois = new ObjectInputStream(in);

                String lr = br.readLine();
                //System.out.println(lr);
                //接收客户端传输过来的用户信息对象
                UserInfo ui = (UserInfo)ois.readObject();
                nickName = chooseLOS(lr,ui);
//                if ("注册".equals(lr)) {
//                    //UserInfo ui = InputUtil.getInputObject(new UserInfo(),"欢迎注册","注册");
//                    nickName = signIn(ui.getUsername(),ui.getPassword(),ui.getNickname(),ui.getAge());
//                }else if("登录".equals(lr)){
//                    //UserInfo ui = InputUtil.getInputObject(new UserInfo(),"欢迎登录","登录");
//                    nickName = login(ui.getUsername(),ui.getPassword());
//                }

//                //读取客户端发过来的第一行字符串作为昵称
//                nickName = br.readLine();

                //通过socket获取输出流，用于将消息发送给客户端
                OutputStream os = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(os);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);

//                synchronized (allOut) {
//                    //将该客户端的输出流存入共享集合
//                    allOut.add(pw);
//                }

                //将该客户端的输出流存入共享集合
                //allOut.add(pw);
                allOut.put(nickName, pw);

                sendMessage(nickName + "(" + host + ")上线了!当前在线人数:" + allOut.size() + "人");

                String line;
                /*
                    readLine方法用于读取来自客户端发送过来的一行字符串。
                    该方法有以下几种现象:
                    1:正常返回一行字符串，正常读取
                    2:返回null，说明客户端进行了四次挥手与服务端断开链接，不会再发送数据
                    3:抛出异常，客户端没有进行四次挥手而异常断开
                 */
                while ((line = br.readLine()) != null) {//读取对方发送过来的一行字符串
                    //将消息输出再广播给所有客户端
                    if (line.startsWith("@")) {
                        sendMessageToSomeOne(line);
                    } else {
                        //将消息广播给所有客户端
                        sendMessage(nickName + "(" + host + ")" + "说:" + line);
                        saveMessage(nickName, null, line);
                    }
                }
            } catch (IOException e) {
                //e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开后的操作

//                synchronized (allOut) {
//                    //将该客户端的输出流从共享集合allOut中删除
//                    allOut.remove(pw);
//                }

                //将该客户端的输出流从共享集合allOut中删除
//                allOut.remove(pw);
                allOut.remove(nickName);

                //广播消息告知所有客户端该用户下线了
                sendMessage(nickName + "(" + host + ")下线了!当前在线人数" + allOut.size() + "人");

                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 广播消息给所有客户端
         *
         * @param message
         */
        public void sendMessage(String message) {
            //先将消息输出到服务端控制台
            System.out.println(message);
//            synchronized (allOut) {
//                //将消息广播给所有客户端
//                for (PrintWriter o : allOut) {
//                    o.println(message);
//                }
//            }
            /*
                将消息广播给所有客户端
                并发安全的集合也不保证与迭代器操作互斥，因此要采取使用集合自身的方法
                forEach来遍历集合，保证与其他操作互斥达到并发安全的目的
             */
//            allOut.forEach(o->o.println(message));
            allOut.values().forEach(o -> o.println(message));
        }

        /**
         * 将消息发送给指定用户
         *
         * @param message
         */
        public void sendMessageToSomeOne(String message) {
            //必要的格式验证
            if (message.matches("@.+:.+")) {
                //1截取出来对方的昵称
                String toNickName = message.substring(1, message.indexOf(":"));
                //2判断对方是否存在
                if (allOut.containsKey(toNickName)) {
                    //3截取出聊天信息并发送给对方
                    String toMessage = message.substring(message.indexOf(":") + 1);
                    //根据对方的昵称获取到他的输出流，将消息单独发送给他
                    allOut.get(nickName).println("你悄悄对" + toNickName + "说:" + toMessage);
                    //私聊发给对方的格式:XXX悄悄和你说:聊天消息
                    allOut.get(toNickName).println(nickName + "悄悄对你说:" + toMessage);
                    saveMessage(nickName, toNickName, toMessage);
                } else {
                    allOut.get(nickName).println("用户[" + toNickName + "]不存在");
                }
            } else {
                allOut.get(nickName).println("私聊格式不正确, 正确格式:@昵称:聊天消息");
            }
        }

        public void saveMessage(String fromUser, String toUser, String content) {

            try (Connection connection = DBUtil.getConnection()) {
                String sql = "INSERT INTO chatinfo(from_user, to_user, content) " +
                        "VALUES(?,?,?)";
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, fromUser);
                ps.setString(2, toUser);
                ps.setString(3, content);
                ps.executeUpdate();

            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        public String login(String userName, String passWord) {
            try (Connection connection = DBUtil.getConnection()) {
                String sql = "SELECT nickname FROM userInfo WHERE username = ? AND password = ?";

                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1, userName);
                ps.setString(2, passWord);
                ResultSet rs = ps.executeQuery();
                if (rs.next()) {
                    String nickname = rs.getString("nickname");
                    allOut.get(nickName).println("登录成功");
                    return nickname;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            return null;
        }

        public String signIn(String userName, String passWord, String nickName, int age) {
            try (Connection connection = DBUtil.getConnection()) {
                String sql = "SELECT userName FROM userinfo WHERE username='"+userName+"'";
                Statement statement = connection.createStatement();
                ResultSet rs = statement.executeQuery(sql);
                while (!rs.next()) {
                    String insertSql = "INSERT INTO userinfo(username, password, nickname, age) VALUES (?,?,?,?)";
                    PreparedStatement ps = connection.prepareStatement(insertSql);
                    ps.setString(1, userName);
                    ps.setString(2, passWord);
                    ps.setString(3, nickName);
                    ps.setInt(4, age);
                    int num = ps.executeUpdate();
                    if (num > 0) {
                        System.out.println("用户注册成功");
                        return nickName;
                    }
                }
                System.out.println("用户已存在");
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            return null;
        }

        public String chooseLOS(String lr, UserInfo ui){
            String nickName;
            System.out.println(lr);
            if ("注册".equals(lr) || lr == "注册") {
                nickName = signIn(ui.getUsername(),ui.getPassword(),ui.getNickname(),ui.getAge());
                System.out.println(nickName);
                return nickName;
            }else if("登录".equals(lr) || lr == "登录"){
                nickName = login(ui.getUsername(),ui.getPassword());
                System.out.println(nickName);
                return nickName;
            }
            return null;
        }
    }
}
